﻿#region Using Directives

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;

    using Csla;
    using Csla.Data;
    using Csla.Reflection;
    using Csla.Server;

    using MarketPulse.BusinessLogic.Core.System;
    using MarketPulse.BusinessLogic.Core.Security;
    using MarketPulse.BusinessLogic.Core;
    using MarketPulse.BusinessLogic.Core.Criteria;

#endregion

namespace MarketPulse.DataAccess.Factory
{
    /// <summary>
    /// DataAccess factory used to fill the ApplicationProfile object.
    /// </summary>
    public class ApplicationProfileFactory : ObjectFactory
    {
        #region Member Variables

            /// <summary>
            /// Property that stores the name of the class.
            /// </summary>
            private static readonly Type mClass = System.Reflection.MethodBase.GetCurrentMethod().DeclaringType;

            /// <summary>
            /// Property that stores the log4net logger for this class.
            /// </summary>
            private static readonly log4net.ILog mLog = log4net.LogManager.GetLogger(mClass);

        #endregion

        #region Methods

            #region Create Methods

                /// <summary>
                /// Method used to create and return a new ApplicationProfile object instance
                /// </summary>
                /// <returns>The newly created ApplicationProfile object</returns>
                [RunLocal]
                public ApplicationProfile Create()
                {
                    // Create a new instance of the object
                    var profile = (ApplicationProfile)MethodCaller.CreateInstance(typeof(ApplicationProfile));

                    profile.Obsolete = false;
                    profile.FailedLoginAttempts = 0;

                    // Mark it as a new object
                    MarkNew(profile);

                    //Check the business rules for this object
                    CheckRules(profile);

                    return profile;
                }

            #endregion

            #region Fetch Methods

                /// <summary>
                /// Method used to receive a combination of profile and user. (ApplicationProfile)
                /// </summary>
                /// <param name="criteria">The criteria object to retrieve data for.</param>
                /// <returns>A filled ApplicationProfile object.</returns>
                public ApplicationProfile Fetch(UsernameAndProfileTypeGUIDCriteria criteria)
                {
                    using (var manager = ContextManager<DataContextObjects.Production.MUM_dev>.GetManager(Database.BusinessSystem))
                    {
                        var applicationProfile = (ApplicationProfile)MethodCaller.CreateInstance(typeof(ApplicationProfile));

                        try
                        {
                            var profileResult = manager.DataContext.UspProfileSelectByUsernameAndProfileType(criteria.Username, criteria.ProfileTypeGUID).Single();

                            // Common properties inherited from MarketPulseBase<ApplicationProfile>
                            LoadProperty(applicationProfile, ApplicationProfile.GUIDProperty, profileResult.ProfileGUID.Value);
                            LoadProperty(applicationProfile, ApplicationProfile.ObsoleteProperty, profileResult.Obsolete.Value);
                            LoadProperty(applicationProfile, ApplicationProfile.InsertUserGUIDProperty, profileResult.InsertUserGUID.Value);
                            LoadProperty(applicationProfile, ApplicationProfile.InsertDateTimeProperty, (SmartDate)profileResult.InsertDateTime);
                            LoadProperty(applicationProfile, ApplicationProfile.UpdateUserGUIDProperty, profileResult.UpdateUserGUID.Value);
                            LoadProperty(applicationProfile, ApplicationProfile.UpdateDateTimeProperty, (SmartDate)profileResult.UpdateDateTime);
                            // Other properties
                            LoadProperty(applicationProfile, ApplicationProfile.UserGUIDProperty, profileResult.UserGUID.Value);
                            LoadProperty(applicationProfile, ApplicationProfile.ProfileTypeGUIDProperty, profileResult.ProfileTypeGUID.Value);
                            LoadProperty(applicationProfile, ApplicationProfile.LanguageGUIDProperty, profileResult.LanguageGUID.Value);
                            LoadProperty(applicationProfile, ApplicationProfile.HomeScreenProperty, profileResult.HomeScreen);
                            LoadProperty(applicationProfile, ApplicationProfile.EnabledProperty, profileResult.Enabled.Value);

                            var userResult = manager.DataContext.UspUserSelect(profileResult.UserGUID.Value).Single();

                            // Other properties
                            LoadProperty(applicationProfile, ApplicationProfile.UsernameProperty, userResult.UserName);
                            LoadProperty(applicationProfile, ApplicationProfile.PasswordProperty, userResult.Password);
                            LoadProperty(applicationProfile, ApplicationProfile.PrivateKeyProperty, MarketPulseSecurity.StringToSHA1(userResult.UserGUID.ToString(), userResult.Password));
                            LoadProperty(applicationProfile, ApplicationProfile.FirstnameProperty, userResult.Firstname);
                            LoadProperty(applicationProfile, ApplicationProfile.LastNameProperty, userResult.Lastname);
                            LoadProperty(applicationProfile, ApplicationProfile.EMailProperty, userResult.EMail);

                            if (userResult.FailedLoginAttempts.HasValue)
                            {
                                LoadProperty(applicationProfile, ApplicationProfile.FailedLoginAttemptsProperty, userResult.FailedLoginAttempts.Value);
                            }

                            // Make sure that CSLA thinks this hasn't changed since being instantiated
                            MarkOld(applicationProfile);
                        }
                        catch (InvalidOperationException ioe)
                        {
                            // Construct error message
                            StringBuilder errorMessage = new StringBuilder();
                            errorMessage.Append("Unable to locate exactly one Profile for the Username = [ ");
                            errorMessage.Append(criteria.Username);
                            errorMessage.Append(" ] for the ProfileType with Guid = [ ");
                            errorMessage.Append(" ]");
                            mLog.Error(errorMessage.ToString(), ioe);
                        }

                        return applicationProfile;
                    }
                }

                /// <summary>
                /// Method used to receive a combination of profile and user. (ApplicationProfile)
                /// </summary>
                /// <param name="criteria">The criteria object to retrieve data for.</param>
                /// <returns>A filled ApplicationProfile object.</returns>
                public ApplicationProfile Fetch(UserGUIDAndProfileTypeGUIDCriteria criteria)
                {
                    using (var manager = ContextManager<DataContextObjects.Production.MUM_dev>.GetManager(Database.BusinessSystem))
                    {
                        var applicationProfile = (ApplicationProfile)MethodCaller.CreateInstance(typeof(ApplicationProfile));

                        try
                        {
                            var profileResult = manager.DataContext.UspProfileSelectByUserGUIDAndProfileType(criteria.UserGUID, criteria.ProfileTypeGUID).Single();

                            // Common properties inherited from MarketPulseBase<ApplicationProfile>
                            LoadProperty(applicationProfile, ApplicationProfile.GUIDProperty, profileResult.ProfileGUID.Value);
                            LoadProperty(applicationProfile, ApplicationProfile.ObsoleteProperty, profileResult.Obsolete.Value);
                            LoadProperty(applicationProfile, ApplicationProfile.InsertUserGUIDProperty, profileResult.InsertUserGUID.Value);
                            LoadProperty(applicationProfile, ApplicationProfile.InsertDateTimeProperty, (SmartDate)profileResult.InsertDateTime);
                            LoadProperty(applicationProfile, ApplicationProfile.UpdateUserGUIDProperty, profileResult.UpdateUserGUID.Value);
                            LoadProperty(applicationProfile, ApplicationProfile.UpdateDateTimeProperty, (SmartDate)profileResult.UpdateDateTime);
                            // Other properties
                            LoadProperty(applicationProfile, ApplicationProfile.UserGUIDProperty, profileResult.UserGUID.Value);
                            LoadProperty(applicationProfile, ApplicationProfile.ProfileTypeGUIDProperty, profileResult.ProfileTypeGUID.Value);
                            LoadProperty(applicationProfile, ApplicationProfile.LanguageGUIDProperty, profileResult.LanguageGUID.Value);
                            LoadProperty(applicationProfile, ApplicationProfile.HomeScreenProperty, profileResult.HomeScreen);
                            LoadProperty(applicationProfile, ApplicationProfile.EnabledProperty, profileResult.Enabled.Value);

                            var userResult = manager.DataContext.UspUserSelect(profileResult.UserGUID.Value).Single();

                            // Other properties
                            LoadProperty(applicationProfile, ApplicationProfile.UsernameProperty, userResult.UserName);
                            LoadProperty(applicationProfile, ApplicationProfile.PasswordProperty, userResult.Password);
                            LoadProperty(applicationProfile, ApplicationProfile.PrivateKeyProperty, MarketPulseSecurity.StringToSHA1(userResult.UserGUID.ToString(), userResult.Password));
                            LoadProperty(applicationProfile, ApplicationProfile.FirstnameProperty, userResult.Firstname);
                            LoadProperty(applicationProfile, ApplicationProfile.LastNameProperty, userResult.Lastname);
                            LoadProperty(applicationProfile, ApplicationProfile.EMailProperty, userResult.EMail);

                            if (userResult.FailedLoginAttempts.HasValue)
                            {
                                LoadProperty(applicationProfile, ApplicationProfile.FailedLoginAttemptsProperty, userResult.FailedLoginAttempts.Value);
                            }

                            // Make sure that CSLA thinks this hasn't changed since being instantiated
                            MarkOld(applicationProfile);

                        }
                        catch (InvalidOperationException ioe)
                        {
                            // Construct error message
                            StringBuilder errorMessage = new StringBuilder();
                            errorMessage.Append("Unable to locate exactly one Profile for the UserGUID = [ ");
                            errorMessage.Append(criteria.UserGUID);
                            errorMessage.Append(" ] and the ProfileType with Guid = [ ");
                            errorMessage.Append(" ]");
                            mLog.Error(errorMessage.ToString(), ioe);
                        }

                        return applicationProfile;
                    }
                }

            #endregion

            #region Update Methods

            /// <summary>
            /// Method used to persist an altered ApplicationProfile object in the database
            /// </summary>
            /// <param name="applicationProfile">The ApplicationProfile object to persist</param>
            /// <returns>The ApplicationProfile object potentially altered by the act of persisting it</returns>
            [Transactional(TransactionalTypes.TransactionScope)]
            public ApplicationProfile Update(ApplicationProfile applicationProfile)
            {
                try
                {
                    using (var manager = ContextManager<DataContextObjects.Production.MUM_dev>.GetManager(Database.BusinessSystem))
                    {
                        // Set up some local variables to hold the return values from the stored procedure calls
                        Nullable<Guid> actionUserGUID = applicationProfile.ActionUserGUID.HasValue ? applicationProfile.ActionUserGUID : Guid.Parse("5A2C77D4-C01D-4AB1-977A-10D4D4BB8F18");
                        Nullable<Guid> userGUID = applicationProfile.UserGUID.Equals(Guid.Empty) ? null : applicationProfile.GUID;
                        Nullable<Guid> profileGUID = applicationProfile.GUID.HasValue ? applicationProfile.GUID : null;
                        Nullable<Guid> countryGUID = applicationProfile.CountryGUID.Equals(Guid.Empty) ? null : applicationProfile.CountryGUID;
                        Nullable<Guid> contactDetailsGUID = applicationProfile.GetContactDetails().GUID.HasValue ? null : applicationProfile.GetContactDetails().GUID;
                        Nullable<DateTime> dateTime = null;
                        Nullable<Guid> userContactDetailsGUID = null;

                        // TODO need to replace this later with a list of countryProfiles for the Application Profile (and hence, the user)
                        Nullable<Guid> countryProfileGUID = null;

                        // TODO get Langauge from the Database
                        Guid languageGUID = Guid.Parse("DC23F122-91C0-4EA1-B227-EE16AFD82A2B");
                        applicationProfile.LanguageGUID = languageGUID;

                        if (applicationProfile.IsDirty)
                        {
                            if (applicationProfile.IsNew)
                            {

                                if (userGUID == null)
                                {
                                    // Insert a new User record
                                    manager.DataContext.UspUserInsert(ref userGUID, applicationProfile.Username,
                                                                        applicationProfile.Password, applicationProfile.Firstname,
                                                                        applicationProfile.Lastname, applicationProfile.EMail,
                                                                        applicationProfile.FailedLoginAttempts, applicationProfile.Obsolete,
                                                                        actionUserGUID, ref dateTime);
                                }

                                // Update the existing Profile record
                                manager.DataContext.UspProfileInsert(ref profileGUID, applicationProfile.ProfileTypeGUID,
                                                                        applicationProfile.LanguageGUID, applicationProfile.HomeScreen,
                                                                        userGUID, applicationProfile.Enabled,
                                                                        applicationProfile.LastLogonDateTime,
                                                                        applicationProfile.SuccessfulLogonCount,
                                                                        applicationProfile.Obsolete,
                                                                        actionUserGUID,
                                                                        ref dateTime);

                                // Update the existing Profile record
                                manager.DataContext.UspCountryProfileInsert(ref countryProfileGUID, applicationProfile.CountryGUID,
                                                                        profileGUID,
                                                                        actionUserGUID,
                                                                        ref dateTime);

                                // Get the COntact Details associated with the user
                                ContactDetails temporaryContactDetails = applicationProfile.GetContactDetails();

                                // Update the existing Profile record
                                manager.DataContext.UspContactDetailsInsert(ref contactDetailsGUID, temporaryContactDetails.CountryGUID,
                                                                        temporaryContactDetails.AreaGUID,
                                                                        temporaryContactDetails.ContactDetailsLabel,
                                                                        temporaryContactDetails.ApartmentOrFloorNumber,
                                                                        temporaryContactDetails.HouseNameOrNumber,
                                                                        temporaryContactDetails.AddressLine1,
                                                                        temporaryContactDetails.AddressLine2,
                                                                        temporaryContactDetails.AddressLine3,
                                                                        temporaryContactDetails.AddressLine4,
                                                                        temporaryContactDetails.AddressLine5,
                                                                        temporaryContactDetails.PostCode,
                                                                        temporaryContactDetails.Phone,
                                                                        temporaryContactDetails.Latitude,
                                                                        temporaryContactDetails.Longtitude,
                                                                        temporaryContactDetails.Obsolete,
                                                                        actionUserGUID,
                                                                        ref dateTime);

                                // Load the updated values into the ApplicationProfile object
                                LoadProperty(temporaryContactDetails, ContactDetails.GUIDProperty, contactDetailsGUID.Value);
                                LoadProperty(temporaryContactDetails, ContactDetails.UpdateDateTimeProperty, dateTime.Value);
                                LoadProperty(temporaryContactDetails, ContactDetails.UpdateUserGUIDProperty, actionUserGUID.Value);

                                applicationProfile.SetContactDetails(temporaryContactDetails);

                                 // Update the existing Profile record
                                manager.DataContext.UspUserContactDetailsInsert(ref userContactDetailsGUID, contactDetailsGUID,
                                                                                userGUID,
                                                                                false,
                                                                                actionUserGUID,
                                                                                ref dateTime);

                                // Load the updated values into the ApplicationProfile object
                                LoadProperty(applicationProfile, ApplicationProfile.GUIDProperty, userGUID.Value);
                                LoadProperty(applicationProfile, ApplicationProfile.UserGUIDProperty, userGUID.Value);
                                LoadProperty(applicationProfile, ApplicationProfile.UpdateDateTimeProperty, dateTime.Value);
                                LoadProperty(applicationProfile, ApplicationProfile.UpdateUserGUIDProperty, actionUserGUID.Value);

                            }
                            else
                            {
                                // Update the existing Profile record
                                manager.DataContext.UspProfileUpdate(applicationProfile.GUID, applicationProfile.ProfileTypeGUID,
                                                                        applicationProfile.LanguageGUID, applicationProfile.HomeScreen,
                                                                        applicationProfile.UserGUID, applicationProfile.Enabled,
                                                                        applicationProfile.LastLogonDateTime,
                                                                        applicationProfile.SuccessfulLogonCount,
                                                                        applicationProfile.Obsolete,
                                                                        actionUserGUID, ref dateTime);

                                // Update the existing User record
                                manager.DataContext.UspUserUpdate(applicationProfile.UserGUID, applicationProfile.Username,
                                                                    applicationProfile.Password, applicationProfile.Firstname,
                                                                    applicationProfile.Lastname, applicationProfile.EMail,
                                                                    applicationProfile.FailedLoginAttempts, applicationProfile.Obsolete,
                                                                    actionUserGUID, ref dateTime);

                                // Load the updated values into the ApplicationProfile object
                                LoadProperty(applicationProfile, ApplicationProfile.UpdateDateTimeProperty, dateTime.Value);
                                LoadProperty(applicationProfile, ApplicationProfile.UpdateUserGUIDProperty, actionUserGUID.Value);

                            }

                            // Make sure that CSLA knows this has changed since being instantiated
                            MarkOld(applicationProfile);

                        }
                        return applicationProfile;
                    }
                }
                catch (Exception ex)
                {
                    mLog.Error(ex.ToString());
                    throw ex;
                }

            }

            #endregion

        #endregion
    }
}
